<template>
  <!-- 地图渲染容器 -->
  <div id="map"></div>
</template>

<script setup>
// 导入必要的库
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer.js";
import { onMounted } from "vue";
import * as d3 from "d3";
import { gsap } from "gsap";

// 组件挂载时执行初始化
onMounted(() => {
  // 创建场景
  const scene = new THREE.Scene();

  // 创建环境光 - 模拟全局光照
  const ambientLight = new THREE.AmbientLight(0xd4e7fd, 4);
  scene.add(ambientLight);

  // 创建四个方向光 - 模拟阳光照射效果
  const directionalLight = new THREE.DirectionalLight(0xe8eaeb, 0.2);
  directionalLight.position.set(0, 10, 5);
  const directionalLight2 = directionalLight.clone();
  directionalLight2.position.set(0, 10, -5);
  const directionalLight3 = directionalLight.clone();
  directionalLight3.position.set(5, 10, 0);
  const directionalLight4 = directionalLight.clone();
  directionalLight4.position.set(-5, 10, 0);
  scene.add(directionalLight);
  scene.add(directionalLight2);
  scene.add(directionalLight3);
  scene.add(directionalLight4);

  // 创建透视相机
  const camera = new THREE.PerspectiveCamera(
    75, // 视场角
    window.innerWidth / window.innerHeight, // 宽高比
    0.1, // 近裁剪面
    1000 // 远裁剪面
  );
  // 设置相机初始位置
  camera.position.y = 8;
  camera.position.z = 8;

  // 创建CSS2D渲染器 - 用于渲染标签文字
  const labelRenderer = new CSS2DRenderer();
  labelRenderer.domElement.style.position = "absolute";
  labelRenderer.domElement.style.top = "0px";
  labelRenderer.domElement.style.pointerEvents = "none"; // 允许鼠标事件穿透到3D场景
  labelRenderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById("map").appendChild(labelRenderer.domElement);

  // 创建WebGL渲染器 - 用于渲染3D场景
  const renderer = new THREE.WebGLRenderer({ alpha: true }); // 启用透明背景
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.getElementById("map").appendChild(renderer.domElement);

  // 创建轨道控制器 - 允许用户交互操作相机
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.update();

  // 动画循环
  const animate = () => {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
    labelRenderer.render(scene, camera);
  };
  animate();

  // 响应窗口大小变化
  window.addEventListener("resize", () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
    labelRenderer.setSize(window.innerWidth, window.innerHeight);
  });

  // 加载地图数据
  const url = "http://localhost:5175/shanghai.json";
  fetch(url)
    .then((res) => res.json())
    .then((data) => {
      // 创建地图并添加到场景
      const map = createMap(data);
      scene.add(map);

      let intersect = null; // 当前选中的地图区域

      // 处理点击事件
      window.addEventListener("pointerdown", (event) => {
        // 计算鼠标在标准化设备坐标中的位置
        const mouse = new THREE.Vector2();
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        // 创建射线投射器
        const raycaster = new THREE.Raycaster();
        raycaster.setFromCamera(mouse, camera);

        // 检测与地图对象的交点，过滤掉线条对象
        const intersects = raycaster
          .intersectObjects(map.children)
          .filter((item) => item.object.type !== "Line");

        // 处理交点
        if (intersects.length > 0) {
          // 如果点击的是网格对象
          if (intersects[0].object.type === "Mesh") {
            // 重置之前选中区域的透明度
            if (intersect) isAplha(intersect, 1);
            // 设置当前选中区域
            intersect = intersects[0].object.parent;
            // 设置选中区域的透明度（高亮效果）
            isAplha(intersect, 0.4);
          }
          // 如果点击的是精灵对象（图标）
          if (intersects[0].object.type === "Sprite") {
            console.log(intersects[0].object);
          }
        } else {
          // 如果没有点击到任何对象，重置之前选中区域的透明度
          if (intersect) isAplha(intersect, 1);
        }

        // 设置对象透明度、颜色、纹理和高度的辅助函数
        function isAplha(intersect, opacity) {
          // 定义一个常量高度值，替代之前使用的depth变量
          const RAISED_HEIGHT = 0.1;
          const DEFAULT_HEIGHT = 0;
          const LABEL_HEIGHT_OFFSET = 0.1;
          const SELECTED_FONT_SIZE = '36px'; // 选中状态的字体大小
          const DEFAULT_FONT_SIZE = '12px';  // 默认状态的字体大小

          intersect.children.forEach((item) => {
            if (item.type === "Mesh") {
              item.material.opacity = opacity;

              // 当透明度为0.4时（选中状态），设置为蓝色并应用纹理
              if (opacity === 0.4) {
                // 检查材质是否是数组
                if (Array.isArray(item.material)) {
                  // 为顶部/底部材质应用纹理和颜色
                  item.material[0].color.set('#f5f34c'); // 设置为黄色
                  // item.material[0].map = new THREE.TextureLoader().load('/src/assets/rock_01_diff_1k.jpg');
                  // item.material[0].map.wrapS = THREE.RepeatWrapping;
                  // item.material[0].map.wrapT = THREE.RepeatWrapping;
                  // item.material[0].map.repeat.set(2, 2); // 设置纹理重复次数
                  item.material[0].needsUpdate = true;

                  // 侧面材质也设置为黄色
                  item.material[1].color.set('#f5f34c');
                  item.material[1].needsUpdate = true;
                } else {
                  item.material.color.set('#f5f34c'); // 设置为黄色
                  // item.material.map = new THREE.TextureLoader().load('/src/assets/rock_01_diff_1k.jpg');
                  // item.material.map.wrapS = THREE.RepeatWrapping;
                  // item.material.map.wrapT = THREE.RepeatWrapping;
                  // item.material.map.repeat.set(2, 2);
                  item.material.needsUpdate = true;
                }

              
                // 将区域抬起/升高
                gsap.to(item.position, {
                  z: RAISED_HEIGHT, // 使用常量高度值
                  duration: 0.1, // 动画持续时间
                  ease: "power2.out"
                });

                findDom(intersect.name, SELECTED_FONT_SIZE)

                // 同时调整标签和图标的位置
                intersect.parent.children.forEach(sibling => {
                  // 查找并修改由 createLabel 创建的标签
                  if (sibling.type === "CSS2DObject") {
                    // 修改标签文字大小
                    gsap.to(sibling.element.style, {
                      fontSize: SELECTED_FONT_SIZE,
                      duration: 0.1,
                      ease: "power2.out"
                    });

                    // 可以同时修改其他样式，如颜色、粗细等
                    sibling.element.style.fontWeight = 'bold';
                  }

                  // 调整标签和图标的位置
                  if (sibling.type === "CSS2DObject" || sibling.type === "Sprite") {
                    gsap.to(sibling.position, {
                      z: RAISED_HEIGHT + LABEL_HEIGHT_OFFSET,
                      duration: 0.1,
                      ease: "power2.out"
                    });
                  }
                });
              } else if (opacity === 1) {
                // 当透明度为1时（未选中状态），恢复为原来的颜色并移除纹理
                if (Array.isArray(item.material)) {
                  item.material[0].color.set('#0e0f0f'); // 恢复原来的颜色
                  item.material[0].map = null; // 移除纹理
                  item.material[0].needsUpdate = true;

                  item.material[1].color.set('#484848'); // 恢复侧面原来的红色
                  item.material[1].needsUpdate = true;
                  // updateLabel(map);
                  let mpaData = createMap(data, intersect.name, 50);
                } else {
                  item.material.color.set('#0e0f0f'); // 恢复原来的颜色
                  item.material.map = null; // 移除纹理
                  item.material.needsUpdate = true;
                }

                // 将区域恢复到原来的高度
                gsap.to(item.position, {
                  z: DEFAULT_HEIGHT, // 使用常量默认高度
                  duration: 0.1,
                  ease: "power2.out"
                });
                 findDom(intersect.name, DEFAULT_FONT_SIZE)

                // 同时恢复标签和图标的位置
                intersect.parent.children.forEach(sibling => {
                  // 查找并恢复由 createLabel 创建的标签
                  if (sibling.type === "CSS2DObject") {
                    // 恢复标签文字大小
                    gsap.to(sibling.element.style, {
                      fontSize: DEFAULT_FONT_SIZE,
                      duration: 0.1,
                      ease: "power2.out"
                    });

                    // 恢复其他样式
                    sibling.element.style.fontWeight = 'normal';
                  }

                  // 恢复标签和图标的位置
                  if (sibling.type === "CSS2DObject" || sibling.type === "Sprite") {
                    gsap.to(sibling.position, {
                      z: DEFAULT_HEIGHT + LABEL_HEIGHT_OFFSET,
                      duration: 0.1,
                      ease: "power2.out"
                    });
                  }
                });
              }
            }
          });
        }
      });
    });
});

// 创建墨卡托投影 - 用于将地理坐标转换为屏幕坐标
const offsetXY = d3.geoMercator();


const findDom = (name,fontSize) => {
  let listDom = document.querySelectorAll(`.map-lable`);
  [...listDom].forEach(item => {
    if (item.textContent == name) {
      item.style.fontSize = fontSize
    }
  })
}

/**
 * 创建地图对象
 * @param {Object} data - GeoJSON格式的地图数据
 * @returns {THREE.Object3D} 地图对象
 */
const createMap = (data, labelName, fontSize) => {
  const map = new THREE.Object3D();
  // 设置投影中心点为第一个特征的中心点
  const center = data.features[0].properties.centroid;
  offsetXY.center(center).translate([0, 0]);

  // 处理每个地理特征
  data.features.forEach((feature) => {
    const unit = new THREE.Object3D(); // 每个区域的容器对象
    const { centroid, center, name } = feature.properties; // 获取区域属性
    const { coordinates, type } = feature.geometry; // 获取几何数据
    const point = centroid || center || [0, 0]; // 确定标签位置

    const color = new THREE.Color('#0e0f0f'); // 设置区域颜色
    const depth = 0.5; // 设置区域高度

    // 创建区域标签
    const label = createLabel(name, point, depth, labelName, fontSize);
    // 创建区域图标
    const icon = createIcon(center, depth);

    // 处理坐标数据
    coordinates.forEach((coordinate) => {
      if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
      if (type === "Polygon") fn(coordinate);

      // 创建区域网格和边框
      function fn(coordinate) {
        unit.name = name; // 设置区域名称
        const mesh = createMesh(coordinate, color, depth); // 创建区域网格
        const line = createLine(coordinate, depth); // 创建区域边框线
        unit.add(mesh, ...line); // 添加到容器
      }
    });

    // 添加区域对象、标签和图标到地图
    map.add(unit, label, icon);
    // 设置地图居中
    setCenter(map);
  });
  return map;
};

/**
 * 创建区域网格
 * @param {Array} data - 坐标数据
 * @param {THREE.Color} color - 颜色
 * @param {number} depth - 高度
 * @returns {THREE.Mesh} 网格对象
 */
const createMesh = (data, color, depth) => {
  const shape = new THREE.Shape();
  // 创建形状路径
  data.forEach((item, idx) => {
    const [x, y] = offsetXY(item);

    if (idx === 0) shape.moveTo(x, -y); // 移动到第一个点
    else shape.lineTo(x, -y); // 连接到后续点
  });

  // 挤压设置
  const extrudeSettings = {
    depth: depth, // 高度
    bevelEnabled: false, // 禁用倒角
  };

  // 顶部和底部面的材质设置（保持原有颜色）
  const topBottomMaterial = new THREE.MeshStandardMaterial({
    color: color,
    emissive: 0x000000,
    roughness: 0.45,
    metalness: 0.8,
    transparent: true, // 启用透明
    side: THREE.DoubleSide, // 双面渲染
  });

  // 侧面材质设置（设置为红色）
  const sideMaterial = new THREE.MeshStandardMaterial({
    color: '#484848', // 红色
    emissive: '#707376',
    roughness: 0.25,
    metalness: 1.5,
    transparent: true,
    side: THREE.DoubleSide,
  });

  // 创建几何体
  const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);

  // 使用材质数组：[顶部/底部材质, 侧面材质]
  const mesh = new THREE.Mesh(geometry, [topBottomMaterial, sideMaterial]);

  return mesh;
};

/**
 * 创建区域边框线
 * @param {Array} data - 坐标数据
 * @param {number} depth - 高度
 * @returns {Array} 上下两条边框线
 */
const createLine = (data, depth) => {
  const points = [];
  // 转换坐标并创建点数组
  data.forEach((item) => {
    const [x, y] = offsetXY(item);
    points.push(new THREE.Vector3(x, -y, 0));
  });

  // 创建几何体和材质
  const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
  const uplineMaterial = new THREE.LineBasicMaterial({ color: 0xcecb40 }); // 修改为#cecb40
  const downlineMaterial = new THREE.LineBasicMaterial({ color: 0xcecb40 }); // 修改为#cecb40

  // 创建上下两条线
  const upLine = new THREE.Line(lineGeometry, uplineMaterial);
  const downLine = new THREE.Line(lineGeometry, downlineMaterial);
  downLine.position.z = -0.0001; // 底部线略微向下偏移
  upLine.position.z = depth + 0.0001; // 顶部线略微向上偏移

  return [upLine, downLine];
};

/**
 * 创建区域标签
 * @param {string} name - 区域名称
 * @param {Array} point - 标签位置
 * @param {number} depth - 高度
 * @returns {CSS2DObject} 标签对象
 */
const createLabel = (name, point, depth, labelName, fontSize) => {
  const div = document.createElement("div");
  div.style.color = "#f5f34c";
  div.style.fontSize = "12px";
  div.textContent = name;
  div.className = 'map-lable';

  const label = new CSS2DObject(div);
  label.scale.set(0.01, 0.01, 0.01);

  const [x, y] = offsetXY(point);
  label.position.set(x, -y, depth);

  return label;
};

/**
 * 创建区域图标
 * @param {Array} point - 图标位置
 * @param {number} depth - 高度
 * @returns {THREE.Sprite} 精灵对象
 */
const createIcon = (point, depth) => {
  const url = new URL("", import.meta.url).href;
  const map = new THREE.TextureLoader().load(url);

  const material = new THREE.SpriteMaterial({
    map: map,
    transparent: true,
  });

  const sprite = new THREE.Sprite(material);
  const [x, y] = offsetXY(point);
  sprite.scale.set(0.3, 0.3, 0.3);
  sprite.position.set(x, -y, depth + 0.2);
  sprite.renderOrder = 1; // 设置渲染顺序

  return sprite;
};

/**
 * 设置地图居中
 * @param {THREE.Object3D} map - 地图对象
 */
const setCenter = (map) => {
  map.rotation.x = -Math.PI / 2; // 将地图旋转90度，使其水平显示

  // 计算地图包围盒
  const box = new THREE.Box3().setFromObject(map);
  const center = box.getCenter(new THREE.Vector3());

  // 调整位置使其居中
  const offset = [0, 0];
  map.position.x = map.position.x - center.x - offset[0];
  map.position.z = map.position.z - center.z - offset[1];
};
</script>

<style>
#map {
  background-color: #d4e7fd;
}
</style>
